#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <termios.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>

#include "api.h"

struct termios oldtio;

unsigned int byte_counter = 0;
int baudrate = BAUDRATE;

int decompose_packet(char *packet, char *data) {
	unsigned int file_size = 0;
	char packet_type = packet[0];
	
	if( packet_type == CPACKET_START ) { // starting control packet
		
		if( packet[1] != FILE_SIZE || packet[2] != SIZEOF_INT )
			return -EXIT_FAILURE;
		
		 // extracts the file_size from the 4bytes.
		file_size |= (packet[3] << 24) & 0xFF000000;
		file_size |= (packet[4] << 16) & 0x00FF0000;
		file_size |= (packet[5] <<  8) & 0x0000FF00;
		file_size |= (packet[6] <<  0) & 0x000000FF;
		
		if( packet[7] != FILE_NAME )
			return -EXIT_FAILURE;
		
		int i;
		for(i = 0; i < (int) packet[8]; ++i) {
			data[i] = packet[i+CPACKET_HEADER_SIZE];
		}
		
		#if DEBUG >= 1
		printf("CPACKET_START READ\n");
		#endif
		
		return (int) packet[8];
	}
	else if( packet_type == CPACKET_END) { // ending control packet
		
		if( packet[1] != FILE_SIZE || packet[2] != SIZEOF_INT )
			return -EXIT_FAILURE;
		
		// extracts the file_size from the 4bytes.
		file_size |= (packet[3] << 24) & 0xFF000000;
		file_size |= (packet[4] << 16) & 0x00FF0000;
		file_size |= (packet[5] <<  8) & 0x0000FF00;
		file_size |= (packet[6] <<  0) & 0x000000FF;
		
		if( packet[7] != FILE_NAME || file_size != byte_counter )
			return -EXIT_FAILURE;

		int i;
		for(i = 0; i < (int) packet[8]; ++i) {
			data[i] = packet[CPACKET_HEADER_SIZE+i];
		}
		
		#if DEBUG >= 1
		printf("CPACKET_END READ\n");
		#endif
		
		return 0;// Indicates that the file has been transmited.
	}
	else if( packet_type == DPACKET ) { // Data packet
		int data_size = 0;
		
		data_size |= (packet[2] << 8) & 0xFF00;
		data_size |= (packet[3] << 0) & 0x00FF;
		
		int i;
		for(i = 0; i < data_size; ++i) {
			data[i] = packet[DPACKET_HEADER_SIZE+i];
		}
		
		byte_counter += i;
		
		#if DEBUG >= 1
		printf("DPACKET READ\n");
		#endif
		
		return data_size;
	}

	return -EXIT_FAILURE;
}

// Constructs a control packet and stores it in "packet".
void compose_cpacket(char* file_path, char* packet, int control)
{
	int file_fd = open(file_path, O_RDONLY);
	struct stat sta_s;
	fstat(file_fd, &sta_s);
	unsigned size = sta_s.st_size;
	close(file_fd);
	
	// File size parameter
	packet[0] = -control;
	packet[1] = FILE_SIZE;
	packet[2] = sizeof(size);

	packet[3] = size >> 24;
	packet[4] = size >> 16;
	packet[5] = size >> 8;
	packet[6] = size >> 0;
	
	// File name parameter
	packet[7] = FILE_NAME;
	packet[8] = strlen(file_path);
	
	char *ptr = file_path + (int) packet[8], *ptr2 = ptr;
	while( *ptr-- != '/' );
	ptr += 2;
	memcpy(&(packet[9]), ptr, ptr2 - ptr); // the file name is passed to the control packet without the file path.
}

// Constructs a data packet and stores it in "packet".
void compose_dpacket(char* data, char* packet, int data_size)
{
    int i;
    
    packet[0] = DPACKET;
    packet[1] = 0;
    packet[2] = data_size >> 8;
    packet[3] = data_size >> 0;
    
    for(i = DPACKET_HEADER_SIZE; i < data_size + DPACKET_HEADER_SIZE; i++)
    {
        packet[i] = data[i-DPACKET_HEADER_SIZE];
    }
   
}

int llopen(int port, char flag) {
	struct termios newtio;
	int fd;
	
	// Register if receiver or sender
	_flag = flag;

	if( (fd = open_connection(port)) < 0)
	{
		perror("LLOPEN ERROR");
		exit(EXIT_FAILURE);
	}

	if( tcgetattr(fd,&oldtio) == -1) { /* save current port settings */
		perror("tcgetattr");
		exit(EXIT_FAILURE);
	}

	bzero(&newtio, sizeof(newtio));
	newtio.c_cflag = baudrate | CS8 | CLOCAL | CREAD;
	newtio.c_iflag = IGNPAR;
	newtio.c_oflag = 0;

	/* set input mode (non-canonical, no echo,...) */
	newtio.c_lflag = 0;

	newtio.c_cc[VTIME]    = 0;
	newtio.c_cc[VMIN]     = 1;

	tcflush(fd, TCIOFLUSH);
	
	if ( tcsetattr(fd,TCSANOW,&newtio) == -1) {
		perror("tcsetattr");
		exit(EXIT_FAILURE);
	}

	if( _flag == SENDER ) {
		int timeouts = 0;

		#if DEBUG >= 1
		printf("sender: llopen: WRITING SET...\n");
		#endif

		do {
			write_cframe(fd, C_SET);

			#if DEBUG >= 1
			printf("sender: llopen: SET WRITTEN\n");
			printf("sender: llopen: READING UA...\n");
			#endif
			
		} while( read_cframe(fd, cframe_ua) == FRAME_RD_TIMEOUT && ++timeouts < max_timeouts);

		if( timeouts >= max_timeouts ) {
		    printf("sender: llopen: Max timeouts reached, exiting...\n");
		    exit(EXIT_FAILURE);
		}
		
		#if DEBUG >= 1
		printf("sender: llopen: UA READ\n");
		#endif
	}
	else if( _flag == RECEIVER ) {

		#if DEBUG >= 1
		printf("\n\nreceiver: llopen: READING SET...\n");
		#endif

		read_cframe(fd, cframe_set);

		#if DEBUG >= 1
		printf("receiver: llopen: SET READ\n");
		printf("receiver: llopen: WRITING UA...\n");
		#endif
		
		write_cframe(fd, C_UA);

		#if DEBUG >= 1
		printf("receiver: llopen: UA WRITTEN\n");
		#endif
	}

	return fd;
}

int llclose(int fd) {
    int timeouts = 0;
  
    if( _flag == SENDER ) {

        #if DEBUG >= 1
		printf("sender: llclose: WRITING DISC\n");
        #endif
       
        do {
			write_cframe(fd, C_DISC);
			
			#if DEBUG >= 1
			printf("sender: llclose: DISC WRITTEN\n");
			printf("sender: llclose: READING DISC...\n");
			#endif
			
			timeouts++;
    	} while( read_cframe(fd, cframe_disc) == FRAME_RD_TIMEOUT && ++timeouts < max_timeouts);

		if( timeouts >= max_timeouts ) {
			printf("sender: llclose: Max timeouts reached, exiting...\n");
			exit(EXIT_FAILURE);
		}
		
		#if DEBUG >= 1
		printf("sender: llclose: DISC READ\n");
		printf("sender: llclose: WRITING UA\n");
		#endif

		write_cframe(fd, C_UA);

		#if DEBUG >= 1
		printf("sender: llclose: UA WRITTEN\n");
		#endif
    }
    else if ( _flag == RECEIVER) {

		#if DEBUG >= 1
		printf("\n\nreceiver: llclose: READING DISC...\n");
		#endif

		read_cframe(fd, cframe_disc);

		#if DEBUG >= 1
		printf("receiver: llclose: DISC READ\n");
		printf("receiver: llclose: WRITING DISC...\n");
		#endif

		write_cframe(fd, C_DISC);

		#if DEBUG >= 1
		printf("receiver: llclose: DISC WRITTEN\n");
		printf("receiver: llclose: READING UA\n");
		#endif

		read_cframe(fd, cframe_ua);
		
		#if DEBUG >= 1
		printf("receiver: llclose: UA READ\n");
		#endif
    }

	#if DEBUG >= 1
	printf("##### llclose ends #####\n");
	#endif

    tcsetattr(fd,TCSANOW,&oldtio);
	
    return close(fd);
}

int llwrite(int fd, char *data, int len) {
	int timeouts = 0;
	char *cframe_rr = next_cframe_rr();
	int bytes_written = -EXIT_FAILURE;

	if(len == -CPACKET_START || len == -CPACKET_END) //send control packet
	{
		int p_len = strlen(data) + CPACKET_HEADER_SIZE;
		char cpacket[p_len];
		
        do {
			tcflush(fd, TCIOFLUSH);
			bzero(cpacket, p_len);
			compose_cpacket(data, cpacket, len);
			bytes_written = write_dframe(fd, cpacket, p_len);
			
			#if DEBUG >= 1
			printf("sender: llwrite: DATA FRAME WRITTEN\n");
			printf("sender: llwrite: READING ACK\n");
			#endif
			
    	} while( read_cframe(fd, cframe_rr) == FRAME_RD_TIMEOUT && ++timeouts < max_timeouts);

		if( timeouts >= max_timeouts ) {
			printf("sender: llwrite: Max timeouts reached, exiting...\n");
			exit(EXIT_FAILURE);
		}
	}
	else if( len > 0 ) // send data packet
	{
		int p_len = len + DPACKET_HEADER_SIZE;
		char dpacket[p_len];
		bzero(dpacket, p_len);
		compose_dpacket(data, dpacket, len);
		
        do {
			bzero(dpacket, p_len);
			compose_dpacket(data, dpacket, len);
			tcflush(fd, TCIOFLUSH);
			bytes_written = write_dframe(fd, dpacket, p_len);

			#if DEBUG >= 1
			printf("sender: llwrite: DATA FRAME WRITTEN\n");
			printf("sender: llwrite: READING ACK\n");
			#endif
			
    	} while( read_cframe(fd, cframe_rr) == FRAME_RD_TIMEOUT && ++timeouts < max_timeouts);

		if( timeouts >= max_timeouts ) {
			printf("sender: llwrite: Max timeouts reached, exiting...\n");
			exit(EXIT_FAILURE);
		}
	}
	
	_rr = (_rr == 0 ? 1 : 0);
	
	return bytes_written;
}

int llread(int fd, char *data) {
	
	char packet[PACKET_MAX_SIZE];
	int bytes_read = -1, rd_frame = 0;
	
	while( (rd_frame = read_dframe(fd, packet)) != FRAME_READ ) {
		bzero(packet, PACKET_MAX_SIZE);
		tcflush(fd, TCIOFLUSH);
		write_cframe(fd, C_REJ(_rr));
	}
	
	_rr = (_rr == 0 ? 1 : 0);
	
	write_cframe(fd, C_RR(_rr));
	
	#if DEBUG >= 1
	printf("receiver: llread: DATA FRAME READ\n");
	printf("receiver: llread: ACK WRITTEN\n");
	#endif
	
	bytes_read = decompose_packet(packet, data);
		
	return bytes_read;
}
